﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using Mesuro.Common;
using System.Windows;
using Vector = Mesuro.Common.Vector;
using System.Windows.Media;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using Mesuro.Graphing.Utilities;
using System.Diagnostics;
using Mesuro.Graphing.Plots;
using System.Windows.Threading;
using System.Threading;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

namespace Mesuro.Graphing {
	public class Plot : ContentControl {

		#region DP: Brush (Attached Only)
		public static readonly DependencyProperty BrushProperty = DependencyProperty.RegisterAttached( "Brush", typeof( Brush ), typeof( Plot ), new FrameworkPropertyMetadata( Brushes.Red, FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsRender, BrushChangedStatic ) );
		public static Brush GetBrush( DependencyObject element ) { return (Brush)element.GetValue( BrushProperty ); }
		public static void SetBrush( DependencyObject element, Brush value ) { element.SetValue( BrushProperty, value ); }
		private static void BrushChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: HighlightColor (Attached Only)
		public static readonly DependencyProperty HighlightColorProperty = DependencyProperty.RegisterAttached( "HighlightColor", typeof( Color ), typeof( Plot ), new FrameworkPropertyMetadata( Colors.Black, FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsRender, HighlightColorChangedStatic ) );
		public static Color GetHighlightColor( DependencyObject element ) { return (Color)element.GetValue( HighlightColorProperty ); }
		public static void SetHighlightColor( DependencyObject element, Color value ) { element.SetValue( HighlightColorProperty, value ); }
		private static void HighlightColorChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: PlotName
		public static readonly DependencyProperty PlotNameProperty = DependencyProperty.RegisterAttached( "PlotName", typeof( String ), typeof( Plot ), new FrameworkPropertyMetadata( "Unnamed Plot", FrameworkPropertyMetadataOptions.None, PlotNameChangedStatic, PlotNameCoerceStatic ) );
		public String PlotName {
			get { return (String)GetValue( PlotNameProperty ); }
			set { SetValue( PlotNameProperty, value ); }
		}
		public static String GetPlotName( DependencyObject element ) { return (String)element.GetValue( PlotNameProperty ); }
		public static void SetPlotName( DependencyObject element, String value ) { element.SetValue( PlotNameProperty, value ); }
		private static void PlotNameChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		private static object PlotNameCoerceStatic( DependencyObject sender, object baseValue ) {
			if (baseValue == null) {
				StringBuilder sb = new StringBuilder();
				foreach (IChannelRef cr in GetChannelMap( sender ))
					sb.Append( cr.Name ).Append( "," );
				if (sb.Length > 0)
					sb.Length--;
				return sb.ToString();
			}
			return baseValue;
		}
		#endregion

		#region DP: RenderType
		public static readonly DependencyProperty RenderTypeProperty = DependencyProperty.RegisterAttached( "RenderType", typeof( string ), typeof( Plot ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsRender, RenderTypeChangedStatic ) );
		public string RenderType {
			get {
				try {
					return (string)this.Dispatcher.Invoke( (Func<object>)(() => GetValue( RenderTypeProperty )), DispatcherPriority.Normal );
				} catch {
					return (string)RenderTypeProperty.DefaultMetadata.DefaultValue;
				}
			}
			set { SetValue( RenderTypeProperty, value ); }
		}
		public static string GetRenderType( DependencyObject element ) { return (string)element.GetValue( RenderTypeProperty ); }
		public static void SetRenderType( DependencyObject element, string value ) { element.SetValue( RenderTypeProperty, value ); }
		private static void RenderTypeChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			Plot plot = sender as Plot;
			if (plot != null) {
				plot.RenderDelegate = GraphingRegistry.GetRenderer( (String)e.NewValue );
				plot.Content = GraphingRegistry.CreatePlotContent( plot, (string)e.NewValue );
				plot.OnRenderTypeChanged( (string)e.OldValue, (string)e.NewValue );
			}
		}
		#endregion

		#region DP: HighlightEffect
		public static readonly DependencyProperty HighlightEffectProperty = DependencyProperty.RegisterAttached( "HighlightEffect", typeof( System.Windows.Media.Effects.Effect ), typeof( Plot ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsRender, HighlightEffectChangedStatic ) );
		public System.Windows.Media.Effects.Effect HighlightEffect {
			get { return (System.Windows.Media.Effects.Effect)GetValue( HighlightEffectProperty ); }
			set { SetValue( HighlightEffectProperty, value ); }
		}
		public static System.Windows.Media.Effects.Effect GetHighlightEffect( DependencyObject element ) { return (System.Windows.Media.Effects.Effect)element.GetValue( HighlightEffectProperty ); }
		public static void SetHighlightEffect( DependencyObject element, System.Windows.Media.Effects.Effect value ) { element.SetValue( HighlightEffectProperty, value ); }
		private static void HighlightEffectChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: IsSelected
		public static readonly DependencyProperty IsSelectedProperty = Selector.IsSelectedProperty.AddOwner( typeof( Plot ), new FrameworkPropertyMetadata( false, FrameworkPropertyMetadataOptions.AffectsRender, IsSelectedChangedStatic ) );
		public bool IsSelected {
			get { return (bool)GetValue( IsSelectedProperty ); }
			set { SetValue( IsSelectedProperty, value ); }
		}
		public static bool GetIsSelected( DependencyObject element ) { return (bool)element.GetValue( IsSelectedProperty ); }
		public static void SetIsSelected( DependencyObject element, bool value ) { element.SetValue( IsSelectedProperty, value ); }
		private static void IsSelectedChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			Plot plot = sender as Plot;
			if (plot != null) {
				plot.Effect = ((bool)e.NewValue) ? plot.HighlightEffect : null;
				plot.OnIsSelectedChanged( (bool)e.NewValue );
			}
		}

		public static readonly RoutedEvent IsSelectedChangedEvent = EventManager.RegisterRoutedEvent( "IsSelectedChanged", RoutingStrategy.Bubble, typeof( RoutedPropertyChangedEventHandler<bool> ), typeof( Plot ) );
		public event RoutedPropertyChangedEventHandler<bool> IsSelectedChanged {
			add { AddHandler( IsSelectedChangedEvent, value ); }
			remove { RemoveHandler( IsSelectedChangedEvent, value ); }
		}
		public static void AddIsSelectedChangedHandler( DependencyObject d, RoutedPropertyChangedEventHandler<bool> handler ) {
			if (d != null && d is UIElement)
				((UIElement)d).AddHandler( IsSelectedChangedEvent, handler );
		}
		public static void RemoveIsSelectedChangedHandler( DependencyObject d, RoutedPropertyChangedEventHandler<bool> handler ) {
			if (d != null && d is UIElement)
				((UIElement)d).RemoveHandler( IsSelectedChangedEvent, handler );
		}
		#endregion

		#region DP: ChannelMap
		public static readonly DependencyPropertyKey ChannelMapPropertyKey = DependencyProperty.RegisterAttachedReadOnly( "ChannelMap", typeof( ObservableCollection<IChannelRef> ), typeof( Plot ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsRender, ChannelMapChangedStatic ) );
		public static readonly DependencyProperty ChannelMapProperty = ChannelMapPropertyKey.DependencyProperty;
		public ObservableCollection<IChannelRef> ChannelMap {
			get {
				{
					return (ObservableCollection<IChannelRef>)GetValue( ChannelMapProperty );
				}
			}
			private set { SetValue( ChannelMapPropertyKey, value ); }
		}
		public static ObservableCollection<IChannelRef> GetChannelMap( DependencyObject element ) { return (ObservableCollection<IChannelRef>)element.GetValue( ChannelMapProperty ); }
		private static void ChannelMapChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			sender.CoerceValue( PlotNameProperty );
			Plot plot = sender as Plot;
			if (plot != null) {
				if (e.OldValue != null)
					((ObservableCollection<IChannelRef>)e.OldValue).CollectionChanged -= plot.OnChannelMapChanged;
				if (e.NewValue != null)
					((ObservableCollection<IChannelRef>)e.NewValue).CollectionChanged += plot.OnChannelMapChanged;
			}
		}
		#endregion

		private readonly object limitLock = new object();
		private bool limitsValid = false;
		private Limits[] _limits;
		public Limits[] Limits {
			get {
				if (!limitsValid)
					calcLimits();
				lock (limitLock) //will block if calculator active
					return _limits;
			}
		}

		//Data Stack:

		public ISeries PlotData {
			get { return AxesTransform ?? ChannelTransform ?? FilterTransform ?? RawData; }
		}

		#region DP: AxesTransform
		public static readonly DependencyProperty AxesTransformProperty = DependencyProperty.RegisterAttached( "AxesTransform", typeof( ISeriesTransform ), typeof( Plot ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.None, AxesTransformChangedStatic ) );
		public ISeriesTransform AxesTransform {
			get { return (ISeriesTransform)GetValue( AxesTransformProperty ); }
			set { SetValue( AxesTransformProperty, value ); }
		}
		public static ISeriesTransform GetAxesTransform( DependencyObject element ) { return (ISeriesTransform)element.GetValue( AxesTransformProperty ); }
		public static void SetAxesTransform( DependencyObject element, ISeriesTransform value ) { element.SetValue( AxesTransformProperty, value ); }
		private static void AxesTransformChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			Plot p = sender as Plot;
			if (p != null)
				p.OnAxesTransformChanged( e.OldValue as ISeriesTransform, e.NewValue as ISeriesTransform );

		}
		#endregion

		#region DP: ChannelTransform
		private static readonly DependencyPropertyKey ChannelTransformPropertyKey = DependencyProperty.RegisterAttachedReadOnly( "ChannelTransform", typeof( ISeriesTransform ), typeof( Plot ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.None, ChannelTransformChangedStatic ) );
		public static readonly DependencyProperty ChannelTransformProperty = ChannelTransformPropertyKey.DependencyProperty;
		public ISeriesTransform ChannelTransform {
			get { return (ISeriesTransform)GetValue( ChannelTransformProperty ); }
			protected set { SetValue( ChannelTransformPropertyKey, value ); }
		}
		public static ISeriesTransform GetChannelTransform( DependencyObject element ) { return (ISeriesTransform)element.GetValue( ChannelTransformProperty ); }
		protected static void SetChannelTransform( DependencyObject element, ISeriesTransform value ) { element.SetValue( ChannelTransformProperty, value ); }
		private static void ChannelTransformChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			Plot p = sender as Plot;
			if (p != null)
				p.OnChannelTransformChanged( e.OldValue as ISeriesTransform, e.NewValue as ISeriesTransform );

		}
		#endregion

		#region DP: FilterTransform
		protected static readonly DependencyPropertyKey FilterTransformPropertyKey = DependencyProperty.RegisterAttachedReadOnly( "FilterTransform", typeof( SeriesTransformCollection ), typeof( Plot ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.None, FilterTransformChangedStatic ) );
		public static readonly DependencyProperty FilterTransformProperty = FilterTransformPropertyKey.DependencyProperty;
		public SeriesTransformCollection FilterTransform {
			get { return (SeriesTransformCollection)GetValue( FilterTransformProperty ); }
			protected set { SetValue( FilterTransformPropertyKey, value ); }
		}
		public static SeriesTransformCollection GetFilterTransform( DependencyObject element ) { return (SeriesTransformCollection)element.GetValue( FilterTransformProperty ); }
		protected static void SetFilterTransform( DependencyObject element, SeriesTransformCollection value ) { element.SetValue( FilterTransformProperty, value ); }
		private static void FilterTransformChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			Plot p = sender as Plot;
			if (p != null) {
				ISeriesTransform tx;
				//Old
				tx = e.OldValue as ISeriesTransform;
				if (tx != null) {
					((SeriesTransformCollection)e.OldValue).DataChanged -= p.OnFiltersChanged;
					tx.Source = null;
				}
				//Source
				tx = e.NewValue as ISeriesTransform;
				if (tx != null) {
					tx.Source = p.RawData;
					((SeriesTransformCollection)e.NewValue).DataChanged += p.OnFiltersChanged;
				}
				//Dest
				tx = p.ChannelTransform ?? p.AxesTransform;
				if (tx != null)
					tx.Source = e.NewValue as ISeries;
			}
		}
		#endregion

		#region DP: RawData
		private static readonly DependencyPropertyKey RawDataPropertyKey = DependencyProperty.RegisterAttachedReadOnly( "RawData", typeof( ISeries ), typeof( Plot ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.None, RawDataChangedStatic ) );
		public static readonly DependencyProperty RawDataProperty = RawDataPropertyKey.DependencyProperty;
		public ISeries RawData {
			get { return (ISeries)GetValue( RawDataProperty ); }
			internal set { SetValue( RawDataPropertyKey, value ); }
		}
		public static ISeries GetRawData( DependencyObject element ) { return (ISeries)element.GetValue( RawDataProperty ); }
		internal static void SetRawData( DependencyObject element, ISeries value ) { element.SetValue( RawDataProperty, value ); }
		private static void RawDataChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			Plot p = sender as Plot;
			if (p != null) {
				p.OnRawDataChanged( e.OldValue as ISeries, e.NewValue as ISeries );
			}
		}
		#endregion

		//:END Data Stack

		private PlotRenderDelegate RenderDelegate;

		public event NotifySeriesChangedEventHandler DataChanged;
		public Plot() {
			RenderType = GraphingRegistry.DefaultRendererType;
			FilterTransform = new SeriesTransformCollection();
			ChannelMap = new ObservableCollection<IChannelRef>();
			HighlightEffect = new System.Windows.Media.Effects.DropShadowEffect() { Color = Colors.Black, BlurRadius = 10, ShadowDepth = 0, Opacity = 1 };
		}
		public Plot( int dimensions )
			: this() {
			for (int i = 0; i < dimensions; i++) {
				ChannelMap.Add( DataSet.GetChannelRef( i.ToString() ) );
			}
		}

		public SeriesTransformCollection.TransformCollectionMarker PushFilter( ISeriesTransform filter ) {
			return FilterTransform.Push( filter );
		}
		public void RemoveFilter( SeriesTransformCollection.TransformCollectionMarker marker ) {
			FilterTransform.Remove( marker );
		}

		protected override Size MeasureOverride( Size constraint ) {
			for (int i = 0; i < VisualTreeHelper.GetChildrenCount( this ); i++)
				((FrameworkElement)VisualTreeHelper.GetChild( this, i )).Measure( constraint );

			return base.MeasureOverride( constraint );
		}

		protected virtual void OnChannelMapChanged( object sender, NotifyCollectionChangedEventArgs e ) {
			int[] idx = new int[ChannelMap.Count];

			RawData = DataSet.GetSeries( ChannelMap[0] );

			for (int i = 0; i < idx.Length; i++)
				idx[i] = RawData.Channels.IndexOf( ChannelMap[i].Channel );

			DelegateSeriesTransform<int[]> dst = DelegateSeriesTransform<int[]>.Create( idx, _channelExtractor );
			dst.OverrideDimensions( idx.Length );
			ChannelTransform = dst;
		}

		private static Vector _channelExtractor( DelegateSeriesTransform<int[]> self, Vector source, int[] context ) {
			Vector ret = new Vector( self.Dimensions );
			for (int i = 0; i < context.Length; i++)
				if (context[i] != -1)
					ret[i] = source[context[i]];
			return ret;
		}

		protected virtual void OnAxesTransformChanged( ISeriesTransform oldValue, ISeriesTransform newValue ) {
			ISeriesTransform tx;
			//Old
			tx = oldValue as ISeriesTransform;
			if (tx != null)
				tx.Source = null;

			//Source
			tx = newValue as ISeriesTransform;
			if (tx != null)
				tx.Source = ChannelTransform ?? FilterTransform ?? RawData;

			//Update
			InvalidateLimits();
			Update();

			if (DataChanged != null)
				DataChanged( this, new NotifySeriesChangedEventArgs( PlotData, NotifySeriesChangedAction.DataReplaced ) );
		}
		protected virtual void OnChannelTransformChanged( ISeriesTransform oldValue, ISeriesTransform newValue ) {
			ISeriesTransform tx;
			//Old
			tx = oldValue as ISeriesTransform;
			if (tx != null)
				tx.Source = null;

			//Source
			tx = newValue as ISeriesTransform;
			if (tx != null)
				tx.Source = FilterTransform ?? RawData;

			//Dest
			tx = AxesTransform;
			if (tx != null)
				tx.Source = newValue as ISeries;

			//Update
			InvalidateLimits();
			Update();

			if (DataChanged != null)
				DataChanged( this, new NotifySeriesChangedEventArgs( PlotData, NotifySeriesChangedAction.ChannelsChanged ) );
		}
		protected virtual void OnFiltersChanged( object Sender, NotifySeriesChangedEventArgs e ) {
			InvalidateLimits();
			Update();

			if (DataChanged != null)
				DataChanged( this, e );
		}
		protected virtual void OnRawDataChanged( ISeries oldValue, ISeries newValue ) {
			ISeriesTransform tx;
			//Dest
			tx = FilterTransform ?? ChannelTransform ?? AxesTransform;
			if (tx != null)
				tx.Source = newValue as ISeries;
			_limits = new Limits[newValue.Dimensions];
			//Update
			InvalidateLimits();
			Update();

			if (DataChanged != null)
				DataChanged( this, new NotifySeriesChangedEventArgs( PlotData, NotifySeriesChangedAction.DataReplaced ) );
		}

		public void InvalidateLimits() {
			limitsValid = false;
		}

		protected virtual void OnRenderTypeChanged( string oldValue, string newValue ) {
			Update();
		}

		protected virtual void OnIsSelectedChanged( bool isSelected ) {
			RaiseEvent( new RoutedPropertyChangedEventArgs<bool>( !isSelected, isSelected, IsSelectedChangedEvent ) );
		}

		void calcLimits() {
			int dim = PlotData.Dimensions;
			Limits[] lims = new Limits[dim];
			foreach (Vector v in PlotData) {
				for (int i = 0; i < dim; i++) {
					lims[i].Union( v[i] );
				}
			}
			lock (limitLock)
				_limits = lims;
		}

		public void Update() {
			if (PlotData == null) return;

			if (!limitsValid)
				calcLimits();

			IPlotContent plotContent = Content as IPlotContent;
			if (plotContent != null && ChannelMap.Count > 0)
				plotContent.Update();
			InvalidateVisual();
		}

		protected override void OnRender( DrawingContext cx ) {
			if (RenderDelegate != null)
				RenderDelegate( this, cx );
		}
	}
	public interface IPlotContent {
		void Update();
	}
}
